package edu.hawaii.halealohacli.kmj;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import javax.xml.datatype.XMLGregorianCalendar;
import org.junit.Test;
import org.wattdepot.client.WattDepotClient;
import org.wattdepot.util.tstamp.Tstamp;

/**
 * Runs a series of test cases for the Baseline class.
 * 
 * @author Jordan Takayama
 */
public class TestBaseline {
  /**
   * The URL of the WattDepot server.
   */
  private String url = "http://server.wattdepot.org:8190/wattdepot/";

  /**
   * A WattDepot client which connects to the server.
   */
  private WattDepotClient sampleClient = new WattDepotClient(url);

  /**
   * The class being tested.
   */
  private Class<Baseline> testClass = Baseline.class;

  /**
   * A Baseline object which is initialized with specific data for each test case.
   */
  private Baseline testObject = null;

  /**
   * A default tower name to use in testing.
   */
  private String sourceName = "Lehua";

  /**
   * A default baseline request date to use in testing. In the event of a server crash (or switching
   * to a different server) developers may need to change this date to a valid one to compensate for
   * the unavailability of data for this date. The current value of testDate is "2011-12-01".
   */
  private String testDate = "2011-12-01";

  /**
   * Formats date values to conform to the format used in XMLGregorianCalendars.
   */
  private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd", 
      Locale.getDefault());

  /**
   * Tests the Baseline constructor for various inputs, including those which intentionally trigger
   * its handled exception(s).
   * 
   * @throws Exception If a method call within the test throws an Exception.
   */
  @Test
  public synchronized void testBaselineConstructor() throws Exception {
    Method useGenerateYesterday = testClass.getDeclaredMethod("generateYesterday", Calendar.class);
    useGenerateYesterday.setAccessible(true);
    testObject = new Baseline();

    Calendar current = Calendar.getInstance();
    Date currentTime = current.getTime();
    String today = DATE_FORMAT.format(currentTime);
    String yesterday = (String) useGenerateYesterday.invoke(testObject, current.clone());
    System.out.println("Testing constructor:");

    testObject = new Baseline(sampleClient, sampleClient.getSource(sourceName), today);
    assertEquals("Trying to call the constructor with the current date should cause baselineDate "
        + "to default to yesterday", yesterday, testObject.getBaselineDate());

    testObject = new Baseline(sampleClient, sampleClient.getSource(sourceName), null);
    assertEquals("Calling the constructor with a null date should cause baselineDate to default "
        + "to yesterday.", yesterday, testObject.getBaselineDate());

    try {
      testObject = new Baseline(sampleClient, null, testDate);
      fail("Failed to throw an exception when given a null Source.");
    }
    catch (Exception e) {
      System.out.println("Correctly threw exception: " + e.getMessage());
    }

    try {
      testObject = new Baseline(sampleClient, sampleClient.getSource(sourceName), testDate);
    }
    catch (Exception e) {
      fail("Constructor threw an exception when given valid input.");
    }
  }

  /**
   * Tests the getter method for the array of baseline energy consumption data.
   * 
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testGetBaselineValues() throws Exception {
    testObject = new Baseline(sampleClient, sampleClient.getSource(sourceName), testDate);

    System.out.println("Testing getBaseline:");
    double[] testData = testObject.getBaselineValues();
    assertNotNull("getBaseline should not return null.", testData);
    System.out.println("Data stored as baseline (in kWh):");
    for (int i = 0; i < testData.length; i++) {
      System.out.println(testData[i]);
    }
  }

  /**
   * Tests the setter method for the array of baseline energy consumption data.
   * 
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testSetBaselineValues() throws Exception {
    // This test only sets the baseline field; full initialization is not needed.
    testObject = new Baseline();
    double[] values = new double[] { 67.23, 45.35, 61.20, 41.30, 10.25, 11.11, 23.46, 19.99 };
    double[] actuallySet;
    System.out.println("Testing setBaseline:");
    testObject.setBaselineValues(values);
    actuallySet = testObject.getBaselineValues();
    if (values.length == actuallySet.length) {
      boolean sameValues = true;
      for (int i = 0; i < values.length; i++) {
        if (values[i] != actuallySet[i]) {
          sameValues = false;
        }
      }
      assertTrue("Values in set array do not match values in array passed to setBaselineValues.",
          sameValues);
    }
    else {
      fail("setBaselineValues: set array's length does not match array passed to method.");
    }
  }

  /**
   * Tests the getter method for baselineDate.
   * 
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testGetBaselineDate() throws Exception {
    testObject = new Baseline(sampleClient, sampleClient.getSource(sourceName), testDate);
    String test = testObject.getBaselineDate();
    
    System.out.println("Testing getBaselineDate:");
    assertEquals("getBaselineDate does not return same date as date used to instantiate Baseline.",
        testDate, test);
  }

  /**
   * Tests the setter method for baselineDate.
   * 
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testSetBaselineDate() throws Exception {
    // This test only sets the baselineDate field; full initialization is not needed.
    testObject = new Baseline();
    String test;
    String set = "2011-12-03";

    System.out.println("Testing setBaselineDate:");
    testObject.setBaselineDate(set);
    test = testObject.getBaselineDate();
    assertEquals("baselineDate not set to the date passed to setBaselineDate.", set, test);
  }

  /**
   * Tests the getter method for sourceName.
   * 
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testGetSourceName() throws Exception {
    testObject = new Baseline(sampleClient, sampleClient.getSource(sourceName), testDate);
    String test = testObject.getSourceName();
    
    System.out.println("Testing getSourceName:");
    assertEquals("getSourceName does not return same string used to instantiate Baseline.",
        sourceName, test);
  }
  
  /**
   * Tests the setter method for sourceName.
   * 
   * @throws Exception  If a method call within the test throws an exception.
   */
  @Test
  public void testSetSourceName() throws Exception {
    // This test only sets the sourceName field; full initialization is not needed.
    testObject = new Baseline();
    String test;
    String set = "Mokihana-B";
    
    System.out.println("Testing setSourceName");
    testObject.setSourceName(set);
    test = testObject.getSourceName();
    assertEquals("sourceName not set to the string passed to setSourceName.", set, test);
  }
  
  /**
   * Check that XMLGregorianCalendars with correct information are being returned by
   * generateTimestamp.
   * 
   * @throws NoSuchMethodException If the generateTimestamp method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testGenerateTimestamp() throws NoSuchMethodException, Exception {
    Method testGenerateTimestamp =
        testClass
            .getDeclaredMethod("generateTimestamp", String.class, String.class, TimeZone.class);
    testGenerateTimestamp.setAccessible(true);
    testObject = new Baseline();

    System.out.println("Testing generateTimestamp: ");
    TimeZone testZone = TimeZone.getTimeZone("PST");
    XMLGregorianCalendar testCalendar =
        (XMLGregorianCalendar) testGenerateTimestamp.invoke(testObject, "2009-04-13",
            "T00:00:00.000", testZone);
    XMLGregorianCalendar compareCalendar = Tstamp.makeTimestamp("2009-04-13T00:00:00.000-08:00");
    assertEquals("generateTimestamp should make a timestamp logically equal to a "
        + "timestamp generated based on the same information", compareCalendar, testCalendar);
  }

  /**
   * Checks that correctly formatted strings representing GMT offsets are being returned by
   * gmtOffsetString.
   * 
   * @throws NoSuchMethodException If the gmtOffsetString method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testGmtOffsetString() throws NoSuchMethodException, Exception {
    Method testOffset = testClass.getDeclaredMethod("gmtOffsetString", TimeZone.class);
    testOffset.setAccessible(true);
    testObject = new Baseline();

    System.out.println("Testing gmtOffsetString: ");
    // Test for positive offset < 10 hours.
    assertEquals("GMT offset for Japan (JST) should be +09:00", "+09:00",
        ((String) testOffset.invoke(testObject, TimeZone.getTimeZone("JST"))));
    // Test for positive offset > 10 hours.
    assertEquals("GMT offset for New Zealand (NZ) should be +12:00", "+12:00",
        ((String) testOffset.invoke(testObject, TimeZone.getTimeZone("NZ"))));
    // Test for 0 hours.
    assertEquals("GMT offset for GMT should be +00:00", "+00:00",
        ((String) testOffset.invoke(testObject, TimeZone.getTimeZone("GMT"))));
    // Test for negative offset < 10 hours.
    assertEquals("GMT offset for Pacific Standard Time (PST) should be -08:00", "-08:00",
        ((String) testOffset.invoke(testObject, TimeZone.getTimeZone("PST"))));
    // Test for negative offset > 10 hours.
    assertEquals("GMT offset for Hawaiian Standard Time (HST) should be -10:00", "-10:00",
        ((String) testOffset.invoke(testObject, TimeZone.getTimeZone("HST"))));
  }

  /**
   * Checks that generateYesterday returns expected yyyy-MM-dd format strings for given Calendar and
   * day change inputs.
   * 
   * @throws NoSuchMethodException If the generateYesterday method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testGenerateYesterday() throws NoSuchMethodException, Exception {
    Method testYesterday = testClass.getDeclaredMethod("generateYesterday", Calendar.class);
    Method useGenerateTimestamp =
        testClass
            .getDeclaredMethod("generateTimestamp", String.class, String.class, TimeZone.class);
    testYesterday.setAccessible(true);
    useGenerateTimestamp.setAccessible(true);
    testObject = new Baseline();
    XMLGregorianCalendar testDateAsXml;
    Calendar calendar;
    String test;

    System.out.println("Testing generateYesterday:");
    testDateAsXml =
        (XMLGregorianCalendar) useGenerateTimestamp.invoke(testObject, "2011-04-01",
            "T00:00:00.000", TimeZone.getDefault());
    calendar = testDateAsXml.toGregorianCalendar();
    test = (String) testYesterday.invoke(testObject, calendar);
    assertEquals("Incorrect date returned for previous day.", "2011-03-31", test);

    testDateAsXml =
        (XMLGregorianCalendar) useGenerateTimestamp.invoke(testObject, "2011-03-01",
            "T00:00:00.000", TimeZone.getDefault());
    calendar = testDateAsXml.toGregorianCalendar();
    test = (String) testYesterday.invoke(testObject, calendar);
    assertEquals("Incorrect date returned for previous day.", "2011-02-28", test);
  }
}
